package com.phonetools.commonutils.image

import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Parcel
import android.os.Parcelable
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.util.concurrent.Executors

typealias CompressResult = (isCompressSuccess: Boolean, imgPath: String?, msg: String?) -> Unit

object ImageCompressUtils {

    private val singleWorkThread by lazy {
        Executors.newSingleThreadExecutor()
    }

    fun compress(
        context: Context, imagePath: String,
        config: ImageCompressConfig, compressResult: CompressResult
    ) {
        if (config.enablePixelCompress) {
            try {
                compressImageByPixel(
                    context, imagePath,
                    config, compressResult
                )
            } catch (e: FileNotFoundException) {
                compressResult(false, imagePath, String.format("图片压缩失败,%s"))
                e.printStackTrace()
            }

        } else {
            compressImageByQuality(
                context, BitmapFactory.decodeFile(imagePath),
                imagePath, config, compressResult
            )
        }
    }


    /**
     * 采样率压缩（像素压缩） 改变Bitmap内存大小
     * 按比例缩小图片的像素以达到压缩的目的
     *
     * @param imgPath
     * @return
     */
    @Throws(FileNotFoundException::class)
    private fun compressImageByPixel(
        context: Context, imgPath: String,
        config: ImageCompressConfig, compressResult: CompressResult
    ) {
        if (imgPath.isEmpty()) {
            compressResult(false, imgPath, "要压缩的文件不存在")
            return
        }
        val newOpts = BitmapFactory.Options()
        newOpts.inJustDecodeBounds = true//只读边,不读内容
        BitmapFactory.decodeFile(imgPath, newOpts)
        newOpts.inJustDecodeBounds = false
        val width = newOpts.outWidth
        val height = newOpts.outHeight
        val maxSize = config.maxPixel
        var be = 1
        if (width >= height && width > maxSize) {//缩放比,用高或者宽其中较大的一个数据进行计算
            be = (newOpts.outWidth / maxSize)
            be++
        } else if (width < height && height > maxSize) {
            be = (newOpts.outHeight / maxSize)
            be++
        }
        newOpts.inSampleSize = be//设置采样率
        newOpts.inPreferredConfig = Bitmap.Config.ARGB_8888//该模式是默认的,可不设
        newOpts.inPurgeable = true// 同时设置才会有效
        newOpts.inInputShareable = true//当系统内存不够时候图片自动被回收
        val bitmap = BitmapFactory.decodeFile(imgPath, newOpts)
        if (config.enableQualityCompress) {
            compressImageByQuality(
                context, bitmap, imgPath,
                config, compressResult
            )//压缩好比例大小后再进行质量压缩
        } else {
            val thumbnailFile =
                getThumbnailFile(context, File(imgPath))
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, FileOutputStream(thumbnailFile))
            compressResult(true, thumbnailFile?.path, "压缩成功")
        }
    }

    /**
     * 质量压缩，改变图片在磁盘的文件大小
     * 多线程压缩图片的质量
     *
     * @param bitmap  内存中的图片
     * @param imgPath 图片的保存路径
     */
    private fun compressImageByQuality(
        context: Context, bitmap: Bitmap?, imgPath: String,
        config: ImageCompressConfig, compressResult: CompressResult
    ) {
        if (bitmap == null) {
            compressResult(false, imgPath, "像素压缩失败,bitmap is null")
            return
        }
        val task = Runnable {
            val baos = ByteArrayOutputStream()
            var options = 100
            bitmap.compress(
                Bitmap.CompressFormat.JPEG,
                options, baos
            )//质量压缩方法，把压缩后的数据存放到baos中 (100表示不压缩，0表示压缩到最小)
            while (baos.toByteArray().size > config.maxSize) {//循环判断如果压缩后图片是否大于指定大小,大于继续压缩
                baos.reset()//重置baos即让下一次的写入覆盖之前的内容
                options -= 5//图片质量每次减少5
                if (options <= 5) {
                    options = 5//如果图片质量小于5，为保证压缩后的图片质量，图片最底压缩质量为5
                }
                bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos)//将压缩后的图片保存到baos中
                if (options == 5) {
                    break//如果图片的质量已降到最低则，不再进行压缩
                }
            }

            val thumbnailFile =
                getThumbnailFile(context, File(imgPath))
            //将压缩后的图片保存的本地上指定路径中
            if (thumbnailFile != null) {
                baos.use {
                    thumbnailFile.writeBytes(it.toByteArray())
                    it.flush()
                    compressResult(true, thumbnailFile.path, null)
                }
            } else {
                compressResult(false, imgPath, "质量压缩失败,thumbnailFile is null")
            }
        }
        singleWorkThread.submit(task)
    }

    private fun getThumbnailFile(context: Context, file: File?): File? {
        return if (file == null || !file.exists()) {
            file
        } else getCompressCacheDir(context, file)
    }

    private fun getCompressCacheDir(context: Context, file: File): File {
        context.cacheDir?.let { cacheDir ->
            File(cacheDir, "compress_cache").apply {
                return if (!this.mkdirs() && (!this.exists() || !this.isDirectory)) {
                    file
                } else {
                    File(this, file.name)
                }
            }
        }
        return file
    }

}

class ImageCompressConfig private constructor() : Parcelable {

    /**
     * 长或宽不超过的最大像素,单位px
     */
    var maxPixel = 1200
        private set
    /**
     * 压缩到的最大大小，单位B
     */
    var maxSize = 100 * 1024
        private set

    /**
     * 是否启用像素压缩
     */
    var enablePixelCompress = false
        private set
    /**
     * 是否启用质量压缩
     */
    var enableQualityCompress = false
        private set

    /**
     * 是否保留原文件
     */
    var isSaveOriginFile = true
        private set

    constructor(parcel: Parcel) : this() {
        maxPixel = parcel.readInt()
        maxSize = parcel.readInt()
        enablePixelCompress = parcel.readByte() != 0.toByte()
        enableQualityCompress = parcel.readByte() != 0.toByte()
        isSaveOriginFile = parcel.readByte() != 0.toByte()
    }

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeInt(maxPixel)
        parcel.writeInt(maxSize)
        parcel.writeByte(if (enablePixelCompress) 1 else 0)
        parcel.writeByte(if (enableQualityCompress) 1 else 0)
        parcel.writeByte(if (isSaveOriginFile) 1 else 0)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<ImageCompressConfig> {
        override fun createFromParcel(parcel: Parcel): ImageCompressConfig {
            return ImageCompressConfig(parcel)
        }

        override fun newArray(size: Int): Array<ImageCompressConfig?> {
            return arrayOfNulls(size)
        }
    }

    class Builder {

        private val compressConfig by lazy {
            ImageCompressConfig()
        }

        fun setMaxPixel(maxPixel: Int): Builder {
            compressConfig.maxPixel = maxPixel
            return this
        }

        fun setMaxSize(maxSize: Int): Builder {
            compressConfig.maxSize = maxSize
            return this
        }

        fun setEnablePixelCompress(enablePixelCompress: Boolean): Builder {
            compressConfig.enablePixelCompress = enablePixelCompress
            return this
        }

        fun setEnableQualityCompress(enableQualityCompress: Boolean): Builder {
            compressConfig.enableQualityCompress = enableQualityCompress
            return this
        }

        fun isSaveOriginFile(isSaveOriginFile: Boolean): Builder {
            compressConfig.isSaveOriginFile = isSaveOriginFile
            return this
        }

        fun create() = compressConfig

    }
}